package org.dei.perla.rockfall.gui;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;

import org.dei.perla.parser.expressions.ConstantBlob;
import org.dei.perla.parser.expressions.ConstantCastException;
import org.dei.perla.sys.device.fpc.dspic.DSPicFPC;
import org.dei.perla.sys.query.llqruntime.datastructures.Record;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * Classe DataViewer: visualizza i Record provenienti da un DSPicFPC
 */
public class DataViewer extends JInternalFrame implements ActionListener {
	
	private static final long serialVersionUID = 2190271589396068772L;
	
	static int frameCount = 0;
	static int xPos = 30;
	static int yPos = 30;
	
	private XYSeriesCollection dataset;
	
	private final Waiter<Pipe<Record>> pipeWaiter;
	private final Thread pipeWaiterThread;
	private Record record;
	
	private final JTextPane samplingDataTextArea;
	private final JScrollPane samplingScrollPane;
	
	private final ArrayList<SingleEvent> eventsData;
	
	private final JComboBox dataSelectorComboBox;

	/**
	 * Costruttore
	 * @param title Titolo dell'InnerFrame
	 * @param recordPipe Pipe dalla quale ricevere i Record
	 */
	public DataViewer(String title, Pipe<Record> recordPipe) {
		
		// Impostazioni JInnerFrame
		super(title);
		setMaximizable(true);
		setIconifiable(true);
		setResizable(true);
		setClosable(false);
		setBounds(xPos, yPos, 500, 500);
		xPos += 30;
		yPos = xPos;
		getContentPane().setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		
		// ArrayList per memorizzare i dati ricevuti dalle pipe
		eventsData = new ArrayList<SingleEvent>();
		
		// Imposto un grafico preliminare da visualizzare mentre non ho ancora ricevuto alcun dato
		JFreeChart xyChart = null;
        dataset = new XYSeriesCollection();
		
		XYSeries xSeries = new XYSeries("X");
        XYSeries ySeries = new XYSeries("Y");
        XYSeries zSeries = new XYSeries("Z");
		
        xSeries.add(1, 0);
        xSeries.add(2, 0);
        ySeries.add(1, 0);
        ySeries.add(2, 0);
        zSeries.add(1, 0);
        zSeries.add(2, 0);
        
        dataset.addSeries(xSeries);
        dataset.addSeries(ySeries);
        dataset.addSeries(zSeries);
        
        xyChart = ChartFactory.createXYLineChart(title + " Data", "Time", "Value", dataset, PlotOrientation.VERTICAL, true, true, false);
        ChartPanel xyPanel = new ChartPanel(xyChart);
        
        // Aggiungo i pannelli all'interfaccia
        JTabbedPane tabbedPane = new JTabbedPane();
        tabbedPane.addTab("Evento", xyPanel);
        
		// Inizializzazione area di testo per i dati relativi al sampling
		samplingDataTextArea = new JTextPane();
		samplingDataTextArea.setEditable(false);
		samplingScrollPane = new JScrollPane(samplingDataTextArea);
		samplingDataTextArea.setText("Nessun dato da visualizzare");
        tabbedPane.addTab("Dati campionamento", samplingScrollPane);
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 0.5;
        gbc.weighty = 0.5;
        gbc.anchor = GridBagConstraints.NORTH;
        gbc.fill = GridBagConstraints.BOTH;
        this.getContentPane().add(tabbedPane, gbc);
        
        // Inizializzazione selettore dei dati da visualizzare
        dataSelectorComboBox = new JComboBox();
        dataSelectorComboBox.addActionListener(this);
        JPanel panel = new JPanel(new GridBagLayout());
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(new JLabel("Eventi disponibili: "));
        gbc.gridx = 1;
        gbc.gridy = 0;
        panel.add(dataSelectorComboBox, gbc);
        
        gbc.gridx = 0;
        gbc.gridy = 1;
        getContentPane().add(panel, gbc);
        
        // Thread di attesa per i dati sulla pipe
        pipeWaiter = new Waiter<Pipe<Record>>();
        pipeWaiter.start();
        pipeWaiter.addWaitable(recordPipe.getWaitable());
        pipeWaiterThread = new Thread(new Runnable() {
        	public void run() {
        		Waitable<Pipe<Record>> waitable;
        		
        		do {
        			waitable = pipeWaiter.waitNext();
        			record = waitable.getParentComponent().dequeue();
        			if (record == null) {
        				waitable.getParentComponent().stop();
        				pipeWaiter.stop();
        				setTitle("DSPic - Terminated");
        				break;
        			}
        			
        			// Eseguo l'aggiornamento del grafico
        			try {
						javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
							public void run() {
								setData(record);
							}
						});
					} catch (InterruptedException e) {
						System.out.println("Thread swing di aggiornamento dati interfaccia grafica interrotto: " + e.getMessage());
					} catch (InvocationTargetException e) {
						System.out.println("Thread swing di aggiornamento dati interfaccia grafica fallito: " + e.getMessage());
					}
        		} while(true);
        	}
        });
        pipeWaiterThread.start();
	}
	
	/**
	 * Visualizza il record nell'interfaccia grafica
	 * @param record record da visualizzare
	 */
	public void setData(Record record) {
		StringBuffer text = new StringBuffer();
		byte[] rowData = ((ConstantBlob)record.getField("DATA")).getValueBlob();
		
		XYSeries xSeries = new XYSeries("X");
		XYSeries ySeries = new XYSeries("Y");
		XYSeries zSeries = new XYSeries("Z");
		
		// Estraggo i dati dall'array di byte
		int j = 0;
		for (int i = 0; i < rowData.length - 10; i = i + 6) {	
			xSeries.add(j, convertToInt(rowData[i], rowData[i+1]));
			ySeries.add(j, convertToInt(rowData[i+2], rowData[i+3]));
			zSeries.add(j, convertToInt(rowData[i+4], rowData[i+5]));
			j++;
		}
		
		// Rimuovo i valori precedentemente inseriti
		dataset.removeAllSeries();
		
		// Inserisco i nuovi dati
		dataset.addSeries(xSeries);
		dataset.addSeries(ySeries);
		dataset.addSeries(zSeries);
		
		try {
			text.append("Low Sampling Rate Data\n");
			text.append("Attitude X: " + record.getField("ATTITUDEX").getValueInt() + "\n");
			text.append("Attitude Y: " + record.getField("ATTITUDEY").getValueInt() + "\n");
			text.append("Attitude Z: " + record.getField("ATTITUDEZ").getValueInt() + "\n");
			text.append("Crackmeter 1: " + record.getField("CRACKMETER1").getValueInt() + "\n");
			text.append("Crackmeter 2: " + record.getField("CRACKMETER2").getValueInt() + "\n");
			text.append("Temperature: " + record.getField("TEMPERATURE").getValueInt() + "\n");
			
			if (record.getField("ACQUISITION_TYPE").getValueInt() == DSPicFPC.ON_EVENT) {
				text.append("\nDati campionamento\n");
				text.append("Flags: " + record.getField("FLAGS").getValueInt() + "\n");
				text.append("Total events: " + record.getField("NUM_EVENTS").getValueInt() + "\n");
				text.append("Samples: " + record.getField("SAMPLES").getValueInt() + "\n");
				text.append("MagnitudeX: " + record.getField("MAGNITUDEX").getValueInt() + "\n");
				text.append("MagnitudeY: " + record.getField("MAGNITUDEY").getValueInt() + "\n");
				text.append("MagnitudeZ: " + record.getField("MAGNITUDEZ").getValueInt() + "\n");
				text.append("NoiseX: " + record.getField("NOISEX").getValueInt() + "\n");
				text.append("NoiseY: " + record.getField("NOISEY").getValueInt() + "\n");
				text.append("NoiseZ: " + record.getField("NOISEZ").getValueInt() + "\n");
				text.append("PeakX: " + record.getField("PEAKX").getValueInt() + "\n");
				text.append("PeakY: " + record.getField("PEAKY").getValueInt() + "\n");
				text.append("PeakZ: " + record.getField("PEAKZ").getValueInt() + "\n");
			}
		} catch (ConstantCastException e) {
			text.append("Errore nel recupero dei dati dal record: " + e.getMessage());
			System.out.println("Errore nel recupero dei dati dal record: " + e.getMessage());
		}
		samplingDataTextArea.setText(text.toString());
		storeEventData(new SingleEvent(xSeries, ySeries, zSeries, text.toString()));
	}
	
	/**
	 * Memorizza i dati relativi ad un singolo evento ed aggiorna il selettore
	 * @param singleEvent
	 */
	private void storeEventData(SingleEvent singleEvent) {
		eventsData.add(singleEvent);
		dataSelectorComboBox.addItem(eventsData.size());
		dataSelectorComboBox.setSelectedIndex(eventsData.size() - 1);
	}
	
	/**
	 * Converte una coppia di byte in un intero
	 * @param b0 byte meno significativo
	 * @param b1 byte piu' significativo
	 * @return numero intero creato dall'unione dei due byte
	 */
	private int convertToInt(byte b0, byte b1) {
		Integer readData;
		
		if(b1 >= 0) {
			readData = (((int)b1 << 8) & 0x0000ff00) | ((int)b0&0x000000ff);
		} else {
			readData = 0xffff0000 | (((int)b1 << 8) & 0x0000ff00) | ((int)b0&0x000000ff) ;
		}
		
		return readData;
	}
	
	/**
	 * Metodo di implementazione dell'ActionListener per il selettore dei dati da visualizzare
	 */
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() instanceof JComboBox) {
			SingleEvent event = eventsData.get(((JComboBox)e.getSource()).getSelectedIndex());
			dataset.removeAllSeries();
			dataset.addSeries(event.getXSeries());
			dataset.addSeries(event.getYSeries());
			dataset.addSeries(event.getZSeries());
			samplingDataTextArea.setText(event.getSampleData());
		}
	}
	
	/**
	 * InnerClass per la memorizzazione dei dati relativi ad un singolo evento
	 */
	private final class SingleEvent {
		
		private final XYSeries xSeries;
		private final XYSeries ySeries;
		private final XYSeries zSeries;
		private final String sampleData;
		
		public SingleEvent(XYSeries xSeries, XYSeries ySeries, XYSeries zSeries, String sampleData) {
			this.xSeries = xSeries;
			this.ySeries = ySeries;
			this.zSeries = zSeries;
			this.sampleData = sampleData;
		}
		
		public String getSampleData() {
			return sampleData;
		}
		
		public XYSeries getXSeries() {
			return xSeries;
		}
		
		public XYSeries getYSeries() {
			return ySeries;
		}
		
		public XYSeries getZSeries() {
			return zSeries;
		}
	}
}
